上一篇文章介紹了單元測試的5W,這一篇則是要介紹How,怎麼開始動手寫我們第一個Unit Test。(終於可以寫程式了,笑...)
本篇文章會以Visual Studio為開發工具,以MSTest為Testing framework。
介紹如何從目標物件的方法,建立對應的單元測試。
也會介紹如何從測試程式,來撰寫對應的目標物件。
最後則會說明怎麼透過Visual Studio來觀看程式碼覆蓋率。
上一篇文章:[Day 2]Unit Testing 簡介
本系列文章專區
@既有程式產生單元測試(VS2010)
首先,先建立一個Library專案,以最一般好懂的例子,裡面有一個Calculator的類別,一個Add的公開方法。程式碼如下所示:
public int Add(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
接著VS2010會把畫面直接帶到測試專案,你的測試類別上。(如果測試類別已經存在,新的測試方法會append在最下面)程式碼如下所示:
/// <summary>
///Add 的測試
///</summary>
[TestMethod()]
public void AddTest()
{
Calculator target = new Calculator(); // TODO: 初始化為適當值
int firstNumber = 0; // TODO: 初始化為適當值
int secondNumber = 0; // TODO: 初始化為適當值
int expected = 0; // TODO: 初始化為適當值
int actual;
actual = target.Add(firstNumber, secondNumber);
Assert.AreEqual(expected, actual);
Assert.Inconclusive("驗證這個測試方法的正確性。");
}
可以看到上面的程式碼,貼心的VS2010已經幫我們把測試程式的殼都建好了。我們剛剛選取要測試的方法,是Calculator類別的Add方法,而Add方法,需要兩個int的參數,並回傳一個int的結果。
所以,測試程式上有哪些東西呢?
很簡單吧,這邊不得不提,Visual Studio更貼心的部分是幫你把需要改的部分,加上了//TODO註解,當設定好之後,別忘了把todo註解移除唷。而最後一行Assert.Inconclusive()則是VS2010在自動產生完測試程式後,替開發人員防呆用的。所以寫好測試程式後,執行測試前記得移除Assert.Inconclusive()這一行。
假設外部的使用情境(也就是測試案例),是傳入1與2,並期望Calculator的Add方法回傳為3,那測試程式碼如下所示:
[TestMethod()]
public void AddTest()
{
Calculator target = new Calculator();
int firstNumber = 1;
int secondNumber = 2;
int expected = 3;
int actual;
actual = target.Add(firstNumber, secondNumber);
Assert.AreEqual(expected, actual);
}
執行測試也很簡單,在測試方法上,滑鼠右鍵即有「執行測試」的選項。但因為執行測試很常使用,而且絕大部分執行的時機點,都不是在測試專案上,而是寫完任一段落的production code。因此建議一定要熟記熱鍵,預設熱鍵組合如下:
在測試結果視窗,就能看到各測試方法的結果,以及測試失敗的錯誤訊息跟call stack。
在撰寫單元測試的程式碼時,有個3A原則,來輔助設計測試程式,可以讓測試程式更好懂。3A原則如下:
程式碼上只需要加上註解,可讀性就會提升一些,如下所示:
[TestMethod()]
public void Add_Input_First_1_Second_2_Return_3()
{
//arrange
Calculator target = new Calculator();
int firstNumber = 1;
int secondNumber = 2;
int expected = 3;
//act
int actual;
actual = target.Add(firstNumber, secondNumber);
//assert
Assert.AreEqual(expected, actual);
}
額外補充一下,要記得改的通常還有一個地方,就是測試方法的名稱。因為當測試失敗時,應該要能迅速的由測試方法名稱判定,是哪一個方法或哪一種情境下,目標物件行為不符合預期。
[註1]感覺可以直接產生對應的測試方法,很過癮吧!但這個功能在VS2012被移除了,其中一個原因應該也是希望開發人員是使用TDD的方式進行開發,而不是寫完程式才回過頭來補測試程式。
[註2]在VS2010,可以針對非public的方法進行單元測試,VS2010會透過reflection幫忙產生一個測試目標的Accessor物件。不過這個功能在VS2012也移除了,其中一個原因應該是因為這樣的測試方式,並不符合物件設計原則。針對這一點,後續我會再用一篇文章來進行說明。
@由測試方法產生目標物件行為
假設我們需要Calculator提供一個減法的功能,傳入3,2,則回傳結果應為1。測試程式如下:
[TestMethod()]
public void Minus_Input_First_3_Second_2_Return_1()
{
//arrange
Calculator target = new Calculator();
int firstNumber = 3;
int secondNumber = 2;
int expected = 1;
//act
int actual;
actual = target.Minus(firstNumber, secondNumber);
//assert
Assert.AreEqual(expected, actual);
}
這時因為Calculator類別上,並沒有Minus方法,所以會建置失敗。這時只需要透過滑鼠右鍵,或是在Minus上,選「產生」(預設熱鍵為Ctrl+.),Visual Studio即會在Calculator上產生Minus的方法。
程式碼如下:
public int Minus(int firstNumber, int secondNumber)
{
throw new NotImplementedException();
}
沒錯,由測試程式所產生的production code,也會依據測試程式所給予的變數名稱,來當作方法簽章。當然,這個產生程式的方式,不僅限於測試程式產生production code,而是只要沒有這個類別或這個方法,就都可以透過產生的方式,來產生class/interface/enum,或是property/function。
這時建置已經可以成功,但執行測試時,肯定會跳紅燈。你問我為什麼?因為我還沒發功啊...預設產生的方法內容是throw new NotImplementedException(); 所以執行測試時,就會接到這個exception。
但請相信我,這是好事。到這步驟,您TDD的起手式已經完成,這是紅燈->綠燈->重構循環的第一步:紅燈。
接著,只需要撰寫Minus方法,讓這個紅燈可以變成綠燈即可。任何方式都可以,包括直接return 1;,或許您會覺得我怎麼可能直接return -1呢?但TDD講究的是,滿足測試案例,即代表功能符合預期。當需要滿足其他需求,請增加測試案例。不斷的紅燈、綠燈、重構,與增加測試案例,就代表目標物件越來越符合外部需求,也代表品質在不同場景下,出錯機率越來越低。而且不必再擔心重構時,把程式改壞了,因為每次修改,都有越來越多的測試案例保護,改完馬上就會知道有沒那個地方冒煙了...
當漸漸熟悉這樣的方式之後,就不需要每次都從hard-code開始撰寫,但這個用最簡單的方式滿足測試案例,有幾個好處:
@測試覆蓋率
測試覆蓋率,或程式碼覆蓋率,指的是執行完測試程式後,所有production code被執行到的比率。相關詳細的介紹,請參考小弟去年鐵人賽的文章:[如何提升系統品質-Day24]測試 - Code Coverage
當在Visual Studio中,建立測試專案後,方案底下會有個Local.testsettings檔,點開後選擇「資料和診斷」,啟用「程式碼涵蓋範圍」。如下圖所示:
讀者可能以為這樣就可以看到code coverage,但其實還有個小地方要設定。針對程式碼涵蓋範圍,double click,會跳出期望要算出code coverage的組件,選擇剛剛的Library,選擇套用,這才設定完畢。
設定完執行一次全部的測試,在測試結果的視窗上,可以點選「顯示程式碼涵蓋範圍結果」,即可觀看程式碼涵蓋範圍,展開細節之後,可直接double click方法,即可移至該方法內容上。當有勾選要計算程式碼覆蓋率時,預設跑完測試後,程式碼就會被上色。有執行到的是淺藍色,沒被執行到的則是紅色。如圖所示:
視窗上有個按鈕,可以開關著色,如下圖所示:
@結論
這篇文章其實很淺,目的是為了讓還沒動手寫過單元測試/測試程式的朋友們,可以step by step的動手玩玩看。
不過本篇文章提到的兩個切入點,都是後續文章或環節的重要起手式:
不管是哪一種角度當切入,設計物件時,有一個重要的原則,希望各位讀者用心記住:
「設計物件,應思考外部如何使用這個物件,而不是bottom-up的思考,這個物件要提供哪些功能給外面用」
其實,就像介面導向的原則一樣,只相依於介面,就可以只專注在抽象層面上,而不被實作細節所影響。
2012取消按右鍵「建立單元測試」的選項,不是很適應,即使要推TDD;但是事後做單元測試,也比都沒有做單元測試來的好。
原來是取消了~難怪我怎麼找都找不到~只好乖乖的從目錄上面去新增